Skip to content

Conversation

@jleven
Copy link
Contributor

@jleven jleven commented Oct 7, 2025

TODO

  • Confirm that the way I'm checking isModal is the right way to do it.

What changes are proposed in this pull request?

Two changes:

  1. Stops propagation of the Escape keyDown event when looker is a Modal
  2. Using the Escape key to clear selections in the grid now first opens a confirmation modal
image

How is this patch tested? If it is not, please explain why.

Clicked around in the app:

  1. Select samples in the grid
  2. Open modal
  3. Close modal with escape key
  4. Verify samples are still selected

Selecting and Clearing:

  1. Select samples in the grid
  2. Press escape key
  3. Verify confirmation dialog pops up
  4. Verify "cancel" does not clear selections / Verify "ok" does clear selections

When nothing is selected:

  1. Make sure no samples are selected in the grid
  2. Press escape key
  3. Verify no confirmation dialog pops up

Release Notes

Is this a user-facing change that should be mentioned in the release notes?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release
    notes for FiftyOne users.

Fixed bug where grid selections would be cleared when closing a modal using the escape key. Also added a confirmation dialog when clearing grid selections using the escape key.

What areas of FiftyOne does this PR affect?

  • App: FiftyOne application changes
  • Build: Build and test infrastructure changes
  • Core: Core fiftyone Python library changes
  • Documentation: FiftyOne documentation changes
  • Other

Summary by CodeRabbit

  • New Features

    • Adds a confirmation dialog before clearing selected items to prevent accidental loss.
  • Bug Fixes

    • Pressing Escape while a modal is open no longer triggers actions in the underlying grid.
    • Escape-based clearing of selections now requires explicit confirmation.
  • Chores

    • Adds an optional modal flag in component configuration to enable modal-aware keyboard handling.
  • Tests

    • End-to-end test updated to auto-accept the confirmation dialog during Escape-driven selection clearing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Grid now prompts for confirmation before clearing selections when modal is null and selected samples exist; Looker adds an isModal flag and prevents Escape from bubbling by calling stopPropagation() when isModal is true. BaseConfig gains an optional isModal?: boolean. E2E test auto-accepts the confirmation dialog.

Changes

Cohort / File(s) Summary of Changes
Escape handling: Grid
app/packages/core/src/components/Grid/useEscape.ts
Replaced unconditional reset when modal is null with a guarded flow: fetches selectedSamples, shows a confirmation dialog "Are you sure you want to clear your current selection?" if selections exist, and calls reset(fos.selectedSamples) only on confirmation. Adds a TODO noting modal-null may not be the correct trigger after modal close.
Looker: keydown/modal flag
app/packages/looker/src/elements/common/looker.ts, app/packages/looker/src/state.ts, app/packages/state/src/hooks/useCreateLooker.ts
Added optional isModal?: boolean to BaseConfig and propagated isModal into Looker creation. Keydown handling in Looker now calls stopPropagation() for Escape when isModal is true to prevent the Escape event from bubbling further.
E2E: dialog auto-accept
e2e-pw/src/oss/specs/selection.spec.ts
Adds page.once("dialog", dialog => dialog.accept()) just before pressing Escape to auto-accept confirmation dialogs during the selection-clear flow.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant L as Looker (keydown handler / config:isModal)
  participant G as Grid useEscape
  participant D as Confirm Dialog

  rect rgba(230,240,255,0.6)
    note over U,L: Escape handling updated with modal-aware propagation
  end

  U->>L: Press Escape
  alt Looker configured as modal (isModal === true)
    L-->>L: stopPropagation()
    note right of L: Escape consumed by Looker
  else Looker not modal (isModal !== true)
    L->>G: keydown event (Escape) bubbles
    alt modal == null and selectedSamples exist
      G->>D: Show confirm "Are you sure you want to clear your current selection?"
      alt User confirms
        D-->>G: Confirmed
        G-->>G: reset(fos.selectedSamples)
      else User cancels
        D-->>G: Canceled (no reset)
      end
    else modal active or no selections
      G-->>G: No reset
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify the modal-null TODO in useEscape.ts and ensure correct trigger after modal close.
  • Check places that construct BaseConfig for any needed handling of the new optional isModal.
  • Confirm stopPropagation() in Looker doesn't interfere with other global Escape behavior.
  • Ensure the E2E dialog auto-accept change only targets the intended confirmation dialog.

Poem

I tapped Esc with gentle hop and cheer,
A question blinked — selections held dear.
Confirm to clear or choose to keep,
Tiny paws pause, decisions deep.
Rabbit grins; the change is neat. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: stopping Escape event propagation when looker is a Modal, which aligns with the primary objective and code changes across multiple files.
Description check ✅ Passed The description covers all required template sections: proposed changes are clearly detailed, testing methodology is comprehensive and well-documented, and release notes are properly filled with user-facing impact.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch josh/explore-modal-close-selection-clear

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d450206 and 633d88b.

📒 Files selected for processing (1)
  • e2e-pw/src/oss/specs/selection.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e-pw/src/oss/specs/selection.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.9)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.12)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.10)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.11)
  • GitHub Check: e2e / test-e2e
  • GitHub Check: build / build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jleven jleven changed the title Figured out the flow of events. I don't see an easy fix. Stop propagation of the Escape event when looker is a Modal Oct 7, 2025
@jleven jleven changed the title Stop propagation of the Escape event when looker is a Modal Stop propagation of the Escape event when looker is a Modal [FOEPD-862] Oct 7, 2025
@jleven jleven marked this pull request as ready for review October 7, 2025 02:27
@jleven jleven requested a review from a team as a code owner October 7, 2025 02:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/packages/core/src/components/Grid/useEscape.ts (1)

24-26: Replace native confirm() with a React modal component.

Using the browser's native confirm() dialog breaks the application's visual consistency and is blocking, which prevents React from updating during the confirmation.

Consider using the application's modal system instead:

// Import the modal action
import { showConfirmationModal } from "@fiftyone/components";

// Replace confirm() with:
const confirmed = await showConfirmationModal({
  title: "Clear Selections",
  message: "You are about to clear all selections. This cannot be undone. Are you sure?",
  confirmText: "Clear",
  cancelText: "Cancel"
});

if (confirmed) {
  reset(fos.selectedSamples);
}

Note: Verify the exact modal component API in your codebase.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01cd890 and bf3d2c3.

📒 Files selected for processing (2)
  • app/packages/core/src/components/Grid/useEscape.ts (1 hunks)
  • app/packages/looker/src/elements/common/looker.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • app/packages/looker/src/elements/common/looker.ts
  • app/packages/core/src/components/Grid/useEscape.ts
🧬 Code graph analysis (1)
app/packages/core/src/components/Grid/useEscape.ts (1)
app/packages/looker/src/lookers/imavid/ima-vid-frame-samples.ts (1)
  • reset (237-243)
🔇 Additional comments (1)
app/packages/looker/src/elements/common/looker.ts (1)

44-45: Escape key propagation is already stopped synchronously. The update callback (including matchedControl.action) and the subsequent e.stopPropagation() run in the same call stack before the event bubbles, so Escape cannot escape before being stopped.

Comment on lines 41 to 46
// If this is a Modal, we don't want Escape to bubble up to the Grid
// Hack: `lookerOptions` only sets showControl=true when a Modal
const isModal = state.options.showControls === true;
if (e.key === "Escape" && isModal) {
e.stopPropagation();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the modal detection approach and document the coupling.

The comment explicitly labels this as a "Hack" and relies on the assumption that lookerOptions only sets showControls=true for modals. This creates fragile coupling that could break if the options usage changes elsewhere.

Run the following script to verify that showControls is only used for modal contexts:

Additionally, consider:

  1. Extracting an explicit isModal property in the state/options to make this relationship explicit rather than relying on a side effect
  2. Documenting this coupling in both the looker options definition and this usage site

🏁 Script executed:

#!/bin/bash
# Description: Check all usages of showControls to verify it's only used for modals

# Search for all assignments to showControls
rg -n --type=ts -C3 'showControls\s*[:=]' 

# Search for all usages of options.showControls
rg -n --type=ts -C3 'options\.showControls'

Length of output: 10101


Introduce an explicit isModal flag and use it for Escape handling instead of showControls. state.options.showControls is also toggled for hover and initial display, so it isn’t a reliable modal indicator. Add an isModal property to your looker options/state and update this handler to check state.options.isModal rather than showControls.

🤖 Prompt for AI Agents
In app/packages/looker/src/elements/common/looker.ts around lines 41 to 46,
replace the current modal-detection that relies on state.options.showControls
with an explicit state.options.isModal flag: add isModal to the looker options
interface and default initialization (preserve existing showControls behavior),
update any code that constructs/merges looker options to accept and propagate
isModal, and change the Escape key handler to check state.options.isModal (and
call e.stopPropagation() only when true). Ensure type definitions and any
consumers are updated for the new flag and maintain backward compatibility by
defaulting isModal to false when not provided.

@jleven jleven force-pushed the josh/explore-modal-close-selection-clear branch from bf3d2c3 to c1f3ddc Compare October 7, 2025 15:15
Copy link
Contributor

@brimoor brimoor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just gave this a test locally and have a couple comments:

  1. Can we please update to not show the dialog box if there is no current selection?
  2. (for technical reviewers) may need to confirm that the dialog box strategy works in FOE too

1. Stops propagation when looker is a Modal
2. Using the Escape key to clear selections in
   the grid now first opens a confirmation modal

Hack: used `showControls === true` as an isModal check

Ideally, looker would have an explicit isModel flag to check
use it to reliably stopPropagation of the Escape key event
@jleven jleven force-pushed the josh/explore-modal-close-selection-clear branch from f862e4a to 3e9d53d Compare October 25, 2025 04:04
@jleven jleven requested a review from benjaminpkane October 25, 2025 04:07
Copy link
Contributor

@benjaminpkane benjaminpkane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as I would expect. LGTM

Comment on lines +24 to +25
// TODO: modal is always `null` here right after a modal closes, so this isn't the condition we want
if (modal === null && selectedSampleIds.size > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. This seems to work. I assume only if propagation is stopped in looker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think modal === null was put here to try to accomplish what this PR is actually accomplishing. Unfortunately, modal is always null here, so we do need this PR.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e9d53d and d450206.

📒 Files selected for processing (1)
  • e2e-pw/src/oss/specs/selection.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Files:

  • e2e-pw/src/oss/specs/selection.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.12)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.9)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.11)
  • GitHub Check: test / test-python (ubuntu-latest-m, 3.10)
  • GitHub Check: test / test-app
  • GitHub Check: lint / eslint
  • GitHub Check: build / build
  • GitHub Check: e2e / test-e2e

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jleven jleven merged commit ca0a5d6 into develop Nov 6, 2025
14 checks passed
@jleven jleven deleted the josh/explore-modal-close-selection-clear branch November 6, 2025 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants